﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using Sustainalytics.Controversies.BusinessLogic;
using Sustainalytics.DataLayer;
using Sustainalytics.DataLayer.Exceptions.AccountsAndUsers;
using Sustainalytics.Entities;
using Sustainalytics.Entities.ConnectionStrings;

namespace Sustainalytics.Accounts.BusinessLogic
{
    public class AccountWithIpManager : IAccountWithIpManager
    {
        private IPlainBl<AccountIpAuthentication> _plainBl;
        private IUserIpLoginManager _plainBlUser;
        private IAccountsRepository _accountsRepository;
        private IIpDataCheck _ipDataCheck;

        private AccountWithIpManager() { }

        public AccountWithIpManager(IPlainBl<AccountIpAuthentication> plainBl, IUserIpLoginManager plainBlUser,IAccountsRepository accountsRepository,IIpDataCheck ipDataCheck)
        {
            _accountsRepository = accountsRepository;
            _plainBl = plainBl;
            _plainBlUser = plainBlUser;
            _ipDataCheck = ipDataCheck;
        }


        public static IAccountWithIpManager GetInstance(IPlainBl<AccountIpAuthentication> plainBl, IUserIpLoginManager plainBlUser, IAccountsRepository accountsRepository, IIpDataCheck ipDataCheck)
        {
            return new AccountWithIpManager
            {
                _accountsRepository = accountsRepository,
                _plainBl = plainBl,
                _plainBlUser = plainBlUser,
                _ipDataCheck = ipDataCheck
            };
        }
        
        public bool CreateOrUpdate(AccountIpAuthentication auth)
        {
            if(AccountIpAuthentication.IsNullOrEmpty(auth)) throw new ArgumentException("The object sent as parameter is null or empty","auth");
            if(auth.Id==Guid.Empty) throw new ArgumentException("The guid id for account is empty");
            var getAccount = _accountsRepository.GetAccountById(auth.Id);
            if (getAccount == null) throw new AccountNotFoundException(string.Format("Cannot find the account with id: '{0}'", auth.Id), auth.Id);
            auth.AccountName = getAccount.Name;
            //check for ip overlapping
            foreach (var ipData in auth.IpDatas)
            {
                IPAddress adrIn;
                if(!string.IsNullOrEmpty(ipData.IpFrom) &&  !IPAddress.TryParse(ipData.IpFrom,out adrIn)) throw new AccountIpAuthenticationInvalidException(string.Format("The ip adress {0} appears to be invalid",ipData.IpFrom));
                if(string.IsNullOrEmpty(ipData.IpTo)) continue;
                if (!IPAddress.TryParse(ipData.IpTo, out adrIn)) throw new AccountIpAuthenticationInvalidException(string.Format("The ip adress {0} appears to be invalid", ipData.IpTo));
            }
            var ipoverlap = _ipDataCheck.GetIpAuthenticationIntersections(auth);
            if (ipoverlap.Any())
            {
                throw new IpAccountOverlapingException(
                    string.Format("The account {0} has ip that are overlaping with the following accounts {1}", auth.Id,
                        ipoverlap.Select(a => a.Id.ToString()).Aggregate((a, b) => string.Format("{0},{1}", a, b))));
            }


            var authFromMongo = GetExistingAuthenticationInformation(auth);

            if (authFromMongo != null) //exists we are in the update case
            {
                auth.UserId = authFromMongo.UserId;
                auth.UserName = authFromMongo.UserName;
                if (AccountIpAuthentication.IsEmptyIpData(auth))
                    
                {
                    if (!AccountIpAuthentication.IsEmptyIpData(authFromMongo)) ////we remove fake user, ip authentication is no longer available for the account
                    {
                        _plainBlUser.DeleteFakeUserIpData(authFromMongo.UserId);
                        auth.UserId = Guid.Empty;
                        auth.UserName = String.Empty;
                    }
                } //there are ip data
                else
                {
                    if (auth.UserId == Guid.Empty && string.IsNullOrEmpty(auth.UserName)) //
                    {
                        PopulateAccountDataWithUserData(auth);
                    }
                }
            }
            else //is not existent
            {
                PopulateAccountDataWithUserData(auth);
                var validOutput = AccountIpAuthentication.ValidateData(auth);
                if (validOutput.Any())
                {
                    var ex = new AccountIpAuthenticationInvalidException("The object is invalid for persistance");
                    foreach (var dat in validOutput)
                    {
                        ex.Data.Add(dat.Key, dat.Value);
                    }
                    throw ex;
                }
            }

            var output = _plainBl.CreateOrUpdate(auth);
            return output;
        }

        private void PopulateAccountDataWithUserData(AccountIpAuthentication auth)
        {
            var userData = _plainBlUser.GetFakeUserIp(auth.Id,auth.AccountName,(auth.IpDatas.FirstOrDefault()??new IpData(string.Empty,String.Empty)).IpFrom);
            auth.UserId = userData.Id;
            auth.UserName = userData.Email;
        }

        private AccountIpAuthentication GetExistingAuthenticationInformation(AccountIpAuthentication auth)
        {
            var colection = _plainBl.ReadAllWhere(a => a.Id == auth.Id, null, null);
            Debug.WriteLineIf(colection == null, string.Format("The collection for guid {0} is null", auth.Id));
            var authFromMongo = (colection ?? new List<AccountIpAuthentication>()).FirstOrDefault(a => a.Id == auth.Id);
            return authFromMongo;
        }


        public AccountIpAuthentication[] GetAccounts(Guid? id = null)
        {
            if (id.GetValueOrDefault() != Guid.Empty)
            {
                return _plainBl.ReadAllWhere(a => a.Id == id.GetValueOrDefault()).ToArray();
            }
            return _plainBl.ReadAllWhere(a=>a.IpDatas.Any()).ToArray();
        }

        public void DeleteAccount(Guid accountId)
        {
            if (accountId == Guid.Empty) 
                throw new ArgumentException("The guid id for account is empty");

            var user = _plainBlUser.GetFakeUserIp(accountId);
            if(user != null)
            {
                _plainBlUser.DeleteFakeUserIpData(user.Id);
                _ipDataCheck.DeleteIpData(accountId);
            }
        }

        public void RenameAccount(Account account)
        {
            _ipDataCheck.RenameIpData(account.Id, account.Name);
        }
    }




    public interface IUserIpLoginManager
    {
        User GetFakeUserIp(Guid accountId);
        User GetFakeUserIp(Guid ip, string accountName,string firsIpAdress);
        bool DeleteFakeUserIpData(Guid userId);
    }

    public interface IIpDataCheck
    {
        AccountIpAuthentication[] GetIpAuthenticationIntersections(AccountIpAuthentication auth);

        AccountIpAuthentication GetAccountFromIp(string ip);

        bool DeleteIpData(Guid userId);

        void RenameIpData(Guid accountId, string newAcoountName);
    }
}
